package elEMAS.impl;

import java.util.List;

import common.GeneticOperations;

import elEMAS.inf.Agent;
import elEMAS.inf.Island;
import elEMAS.inf.elAgent;

public class elEmasAgent<Genotype> extends EmasAgent<Genotype> implements elAgent<Genotype> {

    private int meetingCounter = 0;
    private int numOfDominated = 0;
    private int numOfWasDominated = 0;
    private int neighboursCounter = 0;
    private int prestige = 0;
    private double avgCrowdness;  
    private ElEmasParameters parameters;
    private EmasElitistIsland<Genotype> elitistIsland;
    
    
    public elEmasAgent(Genotype aGenotype, GeneticOperations<Genotype> aGo, 
            Island<Genotype> ai, EmasElitistIsland<Genotype> elitistIsland, 
            List<Island<Genotype>> availableIslands,
            ElEmasParameters parameters) {
        
        super(ai, aGenotype, aGo, availableIslands, parameters);
        
        this.parameters = parameters;
        this.elitistIsland = elitistIsland;
    }
   
    private double calculateDistance(Agent<Genotype> agent) {
        return (calculateDistance_eu(agent));
    }
    
    private double calculateDistance_eu(Agent<Genotype> agent) {
        
        double this_f1 = this.getFitness(0);
        double this_f2 = this.getFitness(1);
        
        double agent_f1 = agent.getFitness(0);
        double agent_f2 = agent.getFitness(1); // 
        
        double result = Math.sqrt(Math.pow((this_f1-agent_f1), 2) + Math.pow((this_f2-agent_f2), 2));
        return result;
    }
    
    protected void meet() {
        
        double wantMeet = Math.random();

        //log.trace("DENSITY : "+getDensity()+" meetingCounter "+this.meetingCounter+" neigh "+neighboursCounter);
        if(_homeIsland.getAgents().size() > 1) {
            if(wantMeet < 0.9) {
                if(this.getEnergy() >= parameters.basicEnergyQuantum)
                {
                    elAgent<Genotype> partner = (elAgent<Genotype>) this.seekMeet();                  
                    if(partner.acceptMeet(this)) {
                        
                        this.increaseMeetingCounter();
                        partner.increaseMeetingCounter();
                       
                        if (this.checkDomination(partner) == 1) {
                            this.increaseNumOfDominated();
                            partner.increaseNumOfWasDominated();
                        }
                        else if (this.checkDomination(partner) == -1) {
                            this.increaseNumOfWasDominated();
                            partner.increaseNumOfDominated();
                        }
                        
                        double distance = calculateDistance(partner);
                        //System.out.println("meet() : distance = "+distance+" f1 = "+this.getFunc().getf1()+" f2 = "+this.getFunc().getf2()+" f1 = "+partner.getFunc().getf1()+" f2 = "+partner.getFunc().getf2());
                        if (distance < parameters.neighbourDistance) {
                            this.increaseNeighbourCounter();
                            partner.increaseNeighbourCounter();
                            ElEmasAlgorithm.stat_numOfNeighboursMeetings++;
                        }
                        else {
                            ElEmasAlgorithm.stat_numOfNonNeighboursMeetings++;
                        }
                        
                        if (this.getNumOfMeetings() > 4)
                            partner.updateCrowdnessAvg(this.getDensity());
                        if (partner.getNumOfMeetings() > 4)
                            this.updateCrowdnessAvg(partner.getDensity());
                        
                        Boolean iWin = null;
                        if (this.checkDomination(partner) == 1) {
                            iWin = true;
                        } 
                        else if (this.checkDomination(partner) == -1) {
                            iWin = false;
                        }
                        // else if (! (this.getNumOfWasDominated() > 0 && partner.getNumOfWasDominated() > 0)) {
                        //    return;
                        // }
                        else if (this.getNumOfMeetings() < 5 || partner.getNumOfMeetings() < 5) {
                            // remis, nikt nie wygrywa
                        }
                        else if (this.getWasDominatedRatio() < partner.getWasDominatedRatio()) {
                            iWin = true;
                        } 
                        else if (this.getWasDominatedRatio() > partner.getWasDominatedRatio()) {
                            iWin = false;
                        }
                        else if ( this.getDensity() < partner.getDensity() 
                                && partner.getEnergy() > parameters.basicEnergyQuantum
                                && ( partner.getNumOfWasDominated() > 0
                                        || (partner.getNumOfDominated() == 0 && this.getNumOfDominated() == 0)
                                   )
                        ) {
                            iWin = true;
                        }
                        else if ( partner.getDensity() < this.getDensity() 
                                && this.getEnergy() > parameters.basicEnergyQuantum
                                && ( this.getNumOfWasDominated() > 0
                                        || (partner.getNumOfDominated() == 0 && this.getNumOfDominated() == 0)
                                   )
                        ) {
                            iWin = false;
                        }

                        if (iWin != null) {
                            if (iWin == true) {
                                partner.transfer(this);
                                this.increasePrestige();
                            }
                            else if (iWin == false) {
                                this.transfer(partner);
                                partner.increasePrestige();
                            }
                            //System.err.println("Rozsztrzygnięty");
                        }
                        else {
                            /*
                            System.out.println(String.format(
                                    "\n numOfMeetings : this = %d, partner = %d\n"+
                                    "wasDominatedRatio : this = %f, partner = %f\n"+
                                    "density : this = %f, partner = %f\n"+
                                    "numOfWasDominated : this = %d, partner = %d\n"+
                                    "energy : this = %d , partner = %d ",
                                    this.getNumOfMeetings(), partner.getNumOfMeetings(),
                                    this.getWasDominatedRatio(), partner.getWasDominatedRatio(),
                                    this.getDensity(), partner.getDensity(),
                                    this.getNumOfDominated(), partner.getNumOfWasDominated(),
                                    this.getEnergy(), partner.getEnergy()
                                    ));
                            /* */
                        }
                    }
                }
            }
        }
    }
    
    /*
    protected void repro() {
        //log.debug("repro()");
        //System.out.println("repro()");
        double wantRepro = Math.random();

        //prawdopodobienstwo spotkania 0.8
        //jesli ma z kim
        if(this.getNeighbors().size() > 1) {
            if(wantRepro < 0.8) {
                if(this._energy > this._reproductionThreshold) {
                    Agent partner = this.seekRepro();
                    if(partner.acceptRepro(this)) {
                        this.create((elAgent)partner);
                        EmasRunner.stat_numOfBornAgents++;
                    }
                }
            }
        }
    }
    */
    
    @Override
    public void create(Agent<Genotype> aPartner) {
        
        elAgent<Genotype> partner = (elAgent<Genotype>) aPartner;
        
        assert this.getEnergy() > parameters.reproductionThreshold;
        assert aPartner.getEnergy() > parameters.reproductionThreshold;
        
        Agent<Genotype> newAgent1 = null;
        Agent<Genotype> newAgent2 = null;

        List<Genotype> childGenotypes = go.crossover(this.getGenotype(), aPartner.getGenotype());
        if (childGenotypes.size() == 1) {
            childGenotypes.addAll(go.crossover(aPartner.getGenotype(), this.getGenotype()));
        }
        
        newAgent1 = new elEmasAgent<Genotype>(childGenotypes.get(0),go,_homeIsland, elitistIsland, _islands, parameters);
        newAgent1.mut();
        newAgent2 = new elEmasAgent<Genotype>(childGenotypes.get(1),go,_homeIsland, elitistIsland, _islands, parameters);
        newAgent2.mut();

        // ReproOO
        int domination = this.checkDomination(aPartner);
        boolean oneStronglyDominates = true;
        boolean equals = false;
        
        elAgent<Genotype> better = null;
        elAgent<Genotype> worse = null;
        
        if (domination == 1) {
            better = this;
            worse = partner;
        }
        else if (domination == -1) {
            better = partner;
            worse = this;
        }

        if (domination == 0) {
            oneStronglyDominates = false;
            
            if (this.getNumOfDominated() > partner.getNumOfDominated()) {
                better = this;
                worse = partner;
            }
            else 
            if (this.getNumOfDominated() < partner.getNumOfDominated()) {
                better = partner;
                worse = this;
            }
            else {
                equals = true;
            }
            
            if (equals == false && worse.getNumOfWasDominated() == 0) {
                // SWAP
                elAgent<Genotype> temp = better;
                better = worse;
                worse = temp;
            }
        }

        if (oneStronglyDominates) {

            int[] result_transfers = calculateEnergyToTransfer(better.getEnergy(),worse.getEnergy(),parameters.startAgentEnergy,parameters.basicEnergyQuantum);
            better.loseEnergy(result_transfers[0]);
            worse.loseEnergy (result_transfers[1]);

            // tutaj nie wiem czy jeszcze raz przeliczac
            //int[] result_transfers = calculateEnergyToTransfer(better.getEnergy(),worse.getEnergy(),parameters.startAgentEnergy,parameters.basicEnergyQuantum);
            
            better.loseEnergy(result_transfers[0]);
            worse.loseEnergy (result_transfers[1]);
        }
        else {
            if (equals) {
                // prosty sposób obaj tracą po równo
                this.loseEnergy(parameters.startAgentEnergy);
                partner.loseEnergy(parameters.startAgentEnergy);
            }
            else {
                
                int[] result_transfers = calculateEnergyToTransfer2(better.getEnergy(),worse.getEnergy(),parameters.startAgentEnergy,parameters.basicEnergyQuantum);
                better.loseEnergy(result_transfers[0]);
                worse.loseEnergy (result_transfers[1]);

                // tutaj nie wiem czy jeszcze raz przeliczac
                //int[] result_transfers = calculateEnergyToTransfer(better.getEnergy(),worse.getEnergy(),parameters.startAgentEnergy,parameters.basicEnergyQuantum);

                better.loseEnergy(result_transfers[0]);
                worse.loseEnergy (result_transfers[1]);
            }
        }
        assert this.getEnergy() >= 0;
        assert partner.getEnergy() >= 0; 
        
        ElEmasAlgorithm.stat_numOfBornAgents++;
        ElEmasAlgorithm.stat_numOfBornAgents++;
    }
    
    /**
     * Ile energii przekazać w przypadku gdy jest dominacja.
     * @return first number is better transfer energy, second is worse transfer energy
     */
    public static int[] calculateEnergyToTransfer(int better, int worse, int startEnergy, int basicEnergyQuantum) {
        
        int[] result = new int[2];
        int worse_transfer_child = (worse - basicEnergyQuantum) / 2;
        
        // zeby nie dostal za duzo
        worse_transfer_child = (worse_transfer_child > startEnergy) ? startEnergy : worse_transfer_child;
        assert worse_transfer_child >= 0;
        
        int better_transfer_child = startEnergy - worse_transfer_child;
        better_transfer_child = (better_transfer_child > 0) ? better_transfer_child : 0;
        assert better_transfer_child <= better;
        
        assert worse_transfer_child + better_transfer_child == startEnergy;
        result[0] = better_transfer_child;
        result[1] = worse_transfer_child;
        
        return result;
    }
    /**
     * Liczy ile energii przekazać, dla przypadku gdy nie ma silnej dominacji.
     * @return first number is better transfer energy, second is worse transfer energy
     */
    public static int[] calculateEnergyToTransfer2(int better, int worse, int startEnergy, int basicEnergyQuantum) {
        
        int[] result = new int[2];
        int worse_transfer_child = (worse - 2*basicEnergyQuantum) / 2;
        
        // zeby nie dostal za duzo
        worse_transfer_child = (worse_transfer_child > startEnergy) ? startEnergy : worse_transfer_child;
        assert worse_transfer_child >= 0;
        
        int better_transfer_child = startEnergy - worse_transfer_child;
        better_transfer_child = (better_transfer_child > 0) ? better_transfer_child : 0;
        assert better_transfer_child <= better;
        
        assert worse_transfer_child + better_transfer_child == startEnergy;
        result[0] = better_transfer_child;
        result[1] = worse_transfer_child;
        
        return result;
    }

    @Override
    public void increaseMeetingCounter() {
        this.meetingCounter++;
    }

    @Override
    public void increaseNeighbourCounter() {
        this.neighboursCounter++;
    }

    @Override
    public int getNeighboursCounter() {
        return this.neighboursCounter;
    }

    @Override
    public void updateCrowdnessAvg(double density) {
        //System.out.println("AvgCrowdness: "+avgCrowdness+", density: "+density);
        //avgCrowdness = ((meetingCounter - 1) * avgCrowdness + density) / meetingCounter;
        avgCrowdness = (5 * avgCrowdness + density) / 6;
        //System.out.println("wynik         "+avgCrowdness);
        //System.out.println();
    }

    @Override
    public double getWasDominatedRatio() {
        return ((double) this.numOfWasDominated) / this.meetingCounter;
    }

    @Override
    public double getDensity() {
        if (this.meetingCounter == 0)
            return Double.MAX_VALUE;
        else
            return ((double)this.neighboursCounter) / this.meetingCounter;
    }

    @Override
    public int getNumOfDominated() {
        return this.numOfDominated;
    }

    @Override
    public void increasePrestige() {
        this.prestige += parameters.prestigeQuantum;
    }

    @Override
    public void increaseNumOfDominated() {
        this.numOfDominated++;     
    }

    @Override
    public void increaseNumOfWasDominated() {
        this.numOfWasDominated++;
    }

    @Override
    public int getNumOfWasDominated() {
        return this.numOfWasDominated;
    }

    @Override
    public void becomeElitist() {

        double wantBecomeElitist = Math.random();
        
        if (prestige > parameters.elitistPrestigeLimit) {
            if (wantBecomeElitist > 0.8) {
                if (getDensity() > avgCrowdness) {
                    //System.err.println("becomeElitist : staję się elitarny, moja energia to "+this.getEnergy()+" prestiż to "+this.prestige+
                    //        " neighbourCounter = "+neighboursCounter+" > "+avgCrowdness);
                    // tutaj jest inaczej niż w pdf, od razu przekazuje energię
                    transferToEnv(this.getEnergy());
                    elitistIsland.addAgent(this);
                    _homeIsland.removeAgent(this);
                    ElEmasAlgorithm.stat_numOfAgentsThatBecameElitist++;
                    
                    //System.out.println("staje sie elitarny");
                }
                else {
                    //log.debug("becomeElitist : mógłbym stać się elitarnym ale liczba sąsiadów "+neighboursCounter+">"+avgCrowdness);
                    //System.err.println("becomeElitist : mógłbym "+this.toString()+" stać się elitarnym "+prestige+" ale mam mało sąsiadów "+getDensity()+" < "+avgCrowdness);
                }
            }
        }
    }
    
    @Override
    public void LifeStep() {
        if(this._energy <= parameters.dieThreshold) {
            this.transferToEnv(this._energy);
            this.die();
        } else {
            this.migr();
            this.meet();
            this.repro();
            this.becomeElitist();
        }
        // jeśłi jest elitarny to nic nie robi, tylko porówniuje się z nowoprzybyłymi agentami
    }

    @Override
    public int getPrestige() {
        return this.prestige;
    }

    @Override
    public int getNumOfMeetings() {
        return this.meetingCounter;
    }
}
